package org.peterbjornx;

import org.peterbjornx.io.*;
import org.peterbjornx.ui.*;
import java.awt.*;
import java.io.IOException;
import java.io.OutputStream;
import java.io.InputStream;
import java.net.Socket;
import java.nio.IntBuffer;

import org.peterbjornx.formats.*;
import org.peterbjornx.image.ImageUtil;
import org.peterbjornx.gamestate.GameState;
import org.peterbjornx.gamestate.LoginGameState;
import org.peterbjornx.gamestate.SimpleGameState;
import org.peterbjornx.gamestate.LoadingGameState;
import org.peterbjornx.net.LoginHandler;
import org.peterbjornx.net.PacketHandler;
import org.peterbjornx.landscape.LandscapeIndex;
import org.peterbjornx.landscape.Landscape;
import org.peterbjornx.landscape.renderer.AWT2DLandscapeRenderer;
import org.peterbjornx.landscape.renderer.AbstractLandscapeRenderer;
import org.peterbjornx.model.Player;
import org.peterbjornx.util.FrameCanvasImpl;
import org.peterbjornx.graphics.jogl.AbstractJOGLRenderer;
import org.peterbjornx.graphics.jogl.MouseHandler;
import org.peterbjornx.graphics.jogl.KeyHandler;
import org.peterbjornx.graphics.jogl.Vector3d;
import org.peterbjornx.graphics.jogl.vecmath.Vector3f;

import javax.media.opengl.*;
import javax.media.opengl.glu.GLU;

import com.sun.opengl.util.BufferUtil;

public class Client extends AbstractJOGLRenderer {

	protected AbstractFileStore interstore;
	protected AbstractFileStore titlestore;
	protected AbstractFileStore spritestore;
	protected InterfaceSource intersource;
	private InterfaceRenderer interrender;
	private int _width;
    public int _height;
    private int loadingprx;
    private String loadingtext;
    private boolean loading = true;
    public static Client instance;
    public AbstractJagexImageReader titleimgreader;
    private Image titlebg;
    private GameState currentgamestate;
    public LoginHandler loginhandler;
    private Socket gamesocket;
    private OutputStream outstream;
    private InputStream instream;
    private int privileges;
    public PacketHandler packethandler;
    public GameState maingamestate;
    private AbstractFileStore indexstore;
    public DiskFileStore mapsstore;
    private int map_x;
    private int map_y;
    public Landscape mylandscape;
    public AbstractLandscapeRenderer landscaperenderer;
    private AbstractFileStore configstore;
    public int[] pl;
    public int plpos;
    public int myplayerindex;
    public Player myplayer = new Player();
    public int plane;
    public int playerCount;
    public int playerRemoveCount;
    public int[] playersToRemove;
    public int[] playerIndices;
    public Player[] playerArray;
    public RSInputStream[] appeareanceStreams;
    private int maxPlayers;
    public int members;
    private FrameCanvasImpl myFrame;
    private GLDrawable glDrawable;
    private GLContext glContext;
    private GL mygl;
    private boolean shutdown = false;
    public boolean needrepaint = true;
    private boolean reset = true;
    private GLU glu;
    public boolean gl_pickrequest = false;
    public Point pickPoint;
    private static final int BUFSIZE = 512;

    public static void main(String args[]){
        instance = new Client();
        instance.initClientFrame(753,503,"RuneScape Rewrite Client");
        try {

            instance.startup();
            instance.gameloop();
        } catch (IOException e) {
            System.err.println("An error occured during startup");
            e.printStackTrace();
        }
    }

    private void gameloop() {
        createGL(this);
        long lastpaint = 0;
        while (!shutdown){
            if (needrepaint){
                long diff = System.currentTimeMillis() - lastpaint;
                diff /= 100;
                lastpaint = System.currentTimeMillis();
                do_jogl();
                needrepaint = false;
                paint_real(getGraphics());
                getGraphics().setColor(Color.white);
                getGraphics().drawString("FPS:"+diff,10,20);
            }
            long curr = System.currentTimeMillis() - (25 * 10);
            if (lastpaint < curr)
                repaint();
            while (!needrepaint)
                try {
                    Thread.sleep(1);
                    curr = System.currentTimeMillis() - (25 * 10);
                    if (lastpaint < curr)
                        repaint();
                } catch (InterruptedException e) {
                    /*SLEEP*/
                }
        }
    }

      /** Near plane clipping distance. */
      public static double nearPlane = 0.001;

      /** Far plane clipping distance. */
      public static double farPlane = 2000000.0;

    public void positionCam(int x, int y,int Z, double z){
        float scale = 1f;
        float dx = x / scale;
        float dy = ( ( mylandscape.heightmap[Z][x][y] / 16f ) / scale );
        float dz = y / scale;
        Vector3d old = vLook;
        vLook = new Vector3d(dx,dy,dz);
        Vector3d dif = new Vector3d();
        dif.sub(vLook,old);
        vEye = new Vector3d(dx,dy,dz+z);
        reset = true;
    }

    /**
     * Handles all actions that need JOGL
     * NOTE: Dont add JOGL calls anywhere else.
     */
    private void do_jogl() {
        if (loading)
            return;
        if (!currentgamestate.opengl_enabled)
            return;
        if (currentgamestate.needvp_change){
            mygl.glViewport(maingamestate.vp_x,maingamestate.vp_y,maingamestate.vp_w,maingamestate.vp_h);
            currentgamestate.needvp_change = false;
            reset = true;
        }
        if (gl_pickrequest){
           mygl.glRenderMode(GL.GL_SELECT);
        }
        int[] selectBuf = new int[BUFSIZE];
        IntBuffer selectBuffer = BufferUtil.newIntBuffer(BUFSIZE);
        if (reset) {
          mygl.glViewport(maingamestate.vp_x,maingamestate.vp_y,maingamestate.vp_w,maingamestate.vp_h);
          if (gl_pickrequest){
            pickPoint.x+=maingamestate.vp_x;
          }
          mygl.glMatrixMode(GL.GL_PROJECTION);
          mygl.glLoadIdentity();
          glu.gluPerspective(65.0, (double) maingamestate.vp_w / (double) maingamestate.vp_h , nearPlane, farPlane);
          mygl.glMatrixMode(GL.GL_MODELVIEW);
          mygl.glLoadIdentity();
          reset = false;
        }
        mygl.glSelectBuffer(BUFSIZE,selectBuffer);
        mygl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
        mygl.glLoadIdentity();
        updateView(mygl);
        if (gl_pickrequest){
            int viewport[] = new int[4];
            float[] proj = new float[16];
            mygl.glMatrixMode(GL.GL_PROJECTION);
            mygl.glLoadIdentity();
            glu.gluPerspective(65.0, (double) maingamestate.vp_w / (double) maingamestate.vp_h , nearPlane, farPlane);
            updateView(mygl);
            mygl.glGetIntegerv(GL.GL_VIEWPORT, viewport, 0);
            mygl.glGetFloatv(GL.GL_PROJECTION_MATRIX,proj,0);
            mygl.glMatrixMode(GL.GL_PROJECTION);
            mygl.glLoadIdentity();
            glu.gluPickMatrix((double) pickPoint.x, (double)maingamestate.vp_h - pickPoint.y, 5.0, 5.0, viewport, 0);
            updateView(mygl);//mygl.glMultMatrixf(proj,0);
            mygl.glMatrixMode(GL.GL_MODELVIEW);
            mygl.glLoadIdentity();
        }
        displayAxes(mygl);
        if (wireframe)
           mygl.glBegin(GL.GL_LINES);
        else
           mygl.glBegin(GL.GL_QUADS);
        float scale = 1f;
        for (int Z = 0;Z < 1/*Landscape.HEIGHT_COUNT*/;Z++)
            for (int x2 = 0; x2 < (Landscape.MAP_WIDTH-1);x2++)
                for (int y2 = 0; y2 < (Landscape.MAP_HEIGHT-1);y2++){
                    Color c = null;
                    if (mylandscape.overlay[Z][x2][y2] == 0 && mylandscape.underlay[Z][x2][y2] != 0)
                        c = new Color(FloorCollection.cache[mylandscape.underlay[Z][x2][y2] - 1].colour2);
                    else if (mylandscape.overlay[Z][x2][y2] != 0)
                        c = new Color(FloorCollection.cache[mylandscape.overlay[Z][x2][y2] - 1].colour2);
                    if (c == null)
                        continue;
                    float r = c.getRed() /255f;
                    float g = c.getGreen() /255f;
                    float b = c.getBlue()/255f;
                    float xa = x2 / scale;
                    float ya = (mylandscape.heightmap[Z][x2][y2]/16f)/scale;
                    float za = y2 / scale;
                    float xb = (x2+1) / scale;
                    float yb = (mylandscape.heightmap[Z][x2+1][y2]/16f)/scale;
                    float zb = y2 / scale;
                    float xc = (x2+1) / scale;
                    float yc = (mylandscape.heightmap[Z][x2+1][y2+1]/16f)/scale;
                    float zc = (y2+1) / scale;
                    float xd = x2 / scale;
                    float yd = (mylandscape.heightmap[Z][x2][y2+1]/16f)/scale;
                    float zd = (y2+1) / scale;
                    if (gl_pickrequest)
                        mygl.glPushName(x2+y2*Landscape.MAP_WIDTH);
                    if (wireframe)
                       mygl.glBegin(GL.GL_LINES);
                    else
                       mygl.glBegin(GL.GL_QUADS);
                    mygl.glColor3f(r,g,b);
                    mygl.glVertex3f(xa,ya,za);
                    mygl.glVertex3f(xb,yb,zb);
                    mygl.glVertex3f(xc,yc,zc);
                    mygl.glVertex3f(xd,yd,zd);
                    mygl.glEnd();
                }
        if (gl_pickrequest){
            mygl.glFlush();
            int hits = mygl.glRenderMode(GL.GL_RENDER);
            selectBuffer.get(selectBuf);
            processHits(hits, selectBuf);
            gl_pickrequest = false;
            reset = true;
        }
        glDrawable.swapBuffers();

    }
      /*
   * prints out the contents of the selection array.
   */
  private void processHits(int hits, int buffer[])
  {
    int names, ptr = 0;

    System.out.println("hits = " + hits);
    // ptr = (GLuint *) buffer;
    for (int i = 0; i < hits; i++)
    { /* for each hit */
      names = buffer[ptr];
      System.out.println(" number of names for hit = " + names);
      ptr++;
      System.out.println("  z1 is " + buffer[ptr]);
      ptr++;
      System.out.println(" z2 is " + buffer[ptr]);
      ptr++;
      System.out.print("\n   the name is ");
      for (int j = 0; j < names; j++)
      { /* for each name */
        System.out.println("" + buffer[ptr]);
        ptr++;
      }
      System.out.println();
    }
  }
    /**
     * Initializes the OpenGL system
     * Author : JAGeX Ltd for RuneScape HD
     * @param c The canvas to create this for
     */
    private void createGL(Object c)
    {
        glu = new GLU();
        GLCapabilities caps = new GLCapabilities();
        GLDrawableFactory gdf = GLDrawableFactory.getFactory();
        glDrawable = gdf.getGLDrawable(c, caps, null);
        glDrawable.setRealized(true);
        do
        {
            glContext = glDrawable.createContext(null);
            try
            {
                int i = glContext.makeCurrent();
                if(i != 0)
                    break;
            }
            catch(Exception exception) {
                System.err.println("[JOGL Thread] Fatal error while initializing glContext.");
            }
        } while(true);

        mygl = glContext.getGL();
        mygl.glClearColor(215f / 255f, 205f / 255f, 154f / 255f, 1.0f);
        mygl.glClear(16384);
        int k = 0;
        do
        {
            if(k >= 10)
                break;
            try
            {
                glDrawable.swapBuffers();
                break;
            }
            catch(Exception exception1)
            {
                k++;
            }
        } while(true);
        mygl.glClearColor(215f / 255f, 205f / 255f, 154f / 255f, 1.0f);
        mygl.glClear(16384);

        mygl.glViewport(0,0,128,128);
    }

	private Client(){
        reset = false;
        emulateFPS = false;
        vEye = new Vector3d(0.0, 0.0, 0.0);
        vLook = new Vector3d(1.0, 0.0, 1.0);
        vUp = new Vector3d(0.0, 1.0, 0.0);
        vRight = new Vector3d(1.0, 0.0, 0.0);
        lastMousePoint = new Point();
        currentMousePoint = new Point();
        KeyHandler s = new KeyHandler();
        MouseHandler lol =new MouseHandler();
	    lol.thePanel = this;
        lol.myrenderer = this;
        s.myrenderer = this;
	    addKeyListener(s);
	    addMouseListener(lol);
	    addMouseMotionListener(lol);
        maxPlayers = 2048;
        myplayerindex = 2047;
        playerArray = new Player[maxPlayers];
        playerIndices = new int[maxPlayers];
        pl = new int[maxPlayers];
        appeareanceStreams = new RSInputStream[maxPlayers];
        playersToRemove = new int[maxPlayers];
	}

	private void initClientFrame(int width, int height, String game){
		_width = width;
		_height = height;
        myFrame = new FrameCanvasImpl(this);
		myFrame.setSize(width+myFrame.getInsets().left+myFrame.getInsets().right,height+myFrame.getInsets().top+myFrame.getInsets().bottom);
		myFrame.setTitle(game);
		myFrame.setVisible(true);
	}
	public void setloadingtext(String t,int prx)
    {
        loadingtext = t;
        loadingprx = prx;
        repaint();
    }
	private void drawloadingtext(String t, int prx){
		Graphics g = getGraphics();
        String th = "Loading RuneScape , please wait...";
        int barh = 37;
		int barw = 152*2;
		int barcx = barw/2;
		int barcy = barh/2;
		int centerx = _width/2;
		int centery = _height/2;
		int basex = centerx - barcx;
		int basey = centery - barcy;
		int fillx = basex + 2;
		int filly = basey + 2;
		int fillw = prx*3;
		int fillh = barh - 3;
		int textx = centerx - (int) (g.getFontMetrics().getStringBounds(t,g).getWidth()/2);
		int texty = centery+(g.getFontMetrics().getHeight()/2);// /2
        int hdrx = centerx - (int) (g.getFontMetrics().getStringBounds(th,g).getWidth()/2);
		int hdry = basey-5;
        if (titlebg == null){
            g.setColor(Color.black);
            g.fillRect(0,0,_width,_height);
        } else{
            g.drawImage(titlebg,0,0,this);
        }
        g.setColor(Color.black);
        g.fillRect(basex,basey,barw,barh);
		g.setColor(Color.red.darker());
		g.drawRect(basex,basey,barw,barh);
		g.fillRect(fillx,filly,fillw,fillh);
		g.setColor(Color.white);
		g.drawString(t,textx,texty);
        g.drawString(th,hdrx,hdry);
	}
	
	private void startup() throws IOException {
        setFocusTraversalKeysEnabled(false);
		drawloadingtext("Loading data stores - 0%",0);
		titlestore = new DiskFileStore("D:/RSCache/Title");
		setloadingtext("Loading data stores - 25%",5);
		spritestore = new DiskFileStore("D:/RSCache/Sprite");
        setloadingtext("Loading data stores - 35%",7);
		configstore = new DiskFileStore("D:/RSCache/Config");
		setloadingtext("Loading data stores - 50%",10);
		interstore = new DiskFileStore("D:/RSCache/Interface");
        setloadingtext("Loading data stores - 75%",12);
        mapsstore = new DiskFileStore("D:/RSCache/Maps");
		setloadingtext("Loading data stores - 85%",15);
		indexstore = new DiskFileStore("D:/RSCache/Index");
		setloadingtext("Loading data stores - 100%",20);
		RSInterface.setFileStores(interstore,spritestore);
        setloadingtext("Initializing title screen",30);
        titleimgreader = new FileJagexImageReader(titlestore);
        titlebg = ImageUtil.mirror(titleimgreader.getImageJPG("TITLE",this),this);
        setloadingtext("Loading indextables",40);
        LandscapeIndex.initializeIndexTable(indexstore);
        setloadingtext("Loading config",40);
        FloorCollection.unpackConfig(configstore);
		setloadingtext("Loading interfaces",80);
        try {
            RSInterface.unpack();
        } catch (IOException e) {
            drawloadingtext("Error loading interfaces:"+e,80);
            return;
        }
        setloadingtext("Initializing game engine",100);
		intersource = new CacheInterfaceSource();
		interrender = new InterfaceRenderer(0,0,_width,_height,intersource,this);
        //End of general code
        //Start of test specific code
        loginhandler = new LoginHandler(this);
        mylandscape = new Landscape();
        landscaperenderer = new AWT2DLandscapeRenderer();
        setGameState(new LoginGameState(this));
        //End of test specific code
        loading = false;
        repaint();
	}

    public boolean connectgameserver(String ip,int port){
        try {
            gamesocket = new Socket(ip,port);
            gamesocket.setKeepAlive(true);
            instream = gamesocket.getInputStream();
            outstream = gamesocket.getOutputStream();
            loginhandler.setStreams(instream,outstream);
            return true;
        } catch (IOException e) {
            return false;
        }
    }

    public void setGameState(GameState g){
        if (currentgamestate != null){
            removeKeyListener(currentgamestate);
            removeMouseListener(currentgamestate);
        }
        currentgamestate = g;
        addKeyListener(currentgamestate);
        addMouseListener(currentgamestate);
    }

    public void update(Graphics g){
        paint(g);
    }

    public void paint(Graphics g){
        needrepaint = true;
    }

    public void paint_real(Graphics g){
        if (!loading)
           currentgamestate.paint(g);
        else{
            if(loadingtext!=null)
                drawloadingtext(loadingtext,loadingprx);
        }

    }

    public void drawtitleback(Graphics g) {
        g.drawImage(titlebg,0,0,this);
    }

    public void notifylogin() {
        privileges = loginhandler.getPrivileges();
        packethandler = new PacketHandler(this,instream,outstream,loginhandler.getISAACSeed());
        maingamestate = new SimpleGameState(this,outstream,loginhandler.getISAACSeed());
        setGameState(maingamestate);
    }

    public void loadregion(int mapx, int mapy) {
        mapx/=8;
        mapy/=8;
        setGameState(new LoadingGameState(this));
        repaint();
        map_x = mapx;
        map_y = mapy;
        try {
            mylandscape.loadMultiMap(mapx,mapy,mapsstore);
        } catch (IOException e) {
            System.err.println("[Landscape System] An exception occurred while loading maps.");
            e.printStackTrace();
        }
        setGameState(maingamestate);
        maingamestate.notifyloaded();
        repaint();
    }
}